/*
 * AudioTranscoder.cpp
 *
 *  Created on: 2016年5月19日
 *      Author: terry
 */

#include "AudioTranscoder.h"
#include "CLog.h"


namespace av
{

AudioTranscoder::AudioTranscoder():
		m_codecID(),
		m_channels(),
		m_sampleRate(),
		m_decContext(),
		m_context(),
		m_swrContext(),
		m_fifo(),
		m_sampleCount()
{
}

AudioTranscoder::~AudioTranscoder()
{
    close();
}

bool AudioTranscoder::open(const MediaFormat& fmt, AVCodecID codecID, int channels, int sampleRate)
{
    bool done = false;

    m_fmt = fmt;
	m_codecID = codecID;
	m_channels = channels;
	m_sampleRate = sampleRate;

    int rc = openDecoder();

	rc = openEncoder(codecID, channels, sampleRate);
    if (rc != 0)
    {
        close();
    }
    else
    {
        done = true;
    }
    return done;
}

void AudioTranscoder::close()
{
	closeEncoder();
    closeDecoder();
}

bool AudioTranscoder::isOpen()
{
	return isEncoderOpen();
}

bool AudioTranscoder::getMediaFormat(MediaFormat& fmt)
{
	fmt.m_audioCodec = m_codecID;
	fmt.m_channels = m_channels;
	fmt.m_sampleRate = m_sampleRate;

	return true;
}

bool AudioTranscoder::transcode(AVPacket* inPkt, AVPacket* outPkt)
{
	if (inPkt)
	{
		int ret = -1;
		AVPacket pkt;
        av_init_packet(&pkt);

        pkt.data = inPkt->data;
        pkt.size = inPkt->size;
        //av_packet_ref(&pkt, inPkt);

		while (pkt.size > 0)
		{
			AVFramePtr frame(av_frame_alloc(), ffmpeg::AVFrameDeleter());

			int gotFrame = 0;
			int bytes = avcodec_decode_audio4(m_decContext, frame.get(), &gotFrame, &pkt);
			if (bytes <= 0)
			{
				break;
			}

			ret = 0;

			pkt.size -= bytes;
			pkt.data += bytes;

			if (gotFrame)
			{
				convertAndStore(frame);
			}
		}

        //av_packet_unref(&pkt);
	}

	if (getFifoSize() < m_context->frame_size)
	{
		return false;
	}

    int rc = doEncode(outPkt);
	return (rc == 0);
}

AVCodecContext* AudioTranscoder::getCodecContext()
{
	return m_context;
}


int AudioTranscoder::openEncoder(AVCodecID codecID, int channels, int sampleRate)
{
	AVCodec* codec = avcodec_find_encoder(codecID);
	if (!codec)
	{
		return ENOENT;
	}

	m_context = avcodec_alloc_context3(codec);
	if (!m_context)
	{
		return ENOENT;
	}

    if (codecID == AV_CODEC_ID_AAC)
    {
	    m_context->profile = FF_PROFILE_AAC_LOW; //profile.m_audioProfile;
    	//m_context->level = profile.m_audioLevel;
    }

	m_context->channels = channels;
    m_context->channel_layout = av_get_default_channel_layout(channels);
	m_context->sample_rate = sampleRate;
    m_context->sample_fmt = codec->sample_fmts[0];
    m_context->time_base = av_make_q(1, sampleRate);

    m_context->flags |= CODEC_FLAG2_LOCAL_HEADER;

    AVDictionary* options = NULL;
    if (codecID == AV_CODEC_ID_AAC)
    {
        av_dict_set(&options, "strict", "-2", 0);
    }

    int rc = avcodec_open2(m_context, codec, &options);

    av_dict_free(&options);

    if (rc < 0)
    {
        char buf[256];
        av_strerror(rc, buf, 256);

        av_free(m_context);
        m_context = NULL;

        return ENOENT;
    }

    if (m_context->extradata_size > 0)
    {
        m_extradata.assign((char*)m_context->extradata, m_context->extradata_size);
    }

	return 0;
}

void AudioTranscoder::closeEncoder()
{
	if (m_swrContext)
	{
		swr_close(m_swrContext);
		swr_free(&m_swrContext);
		m_swrContext = NULL;
	}

	if (m_context)
	{
		avcodec_close(m_context);
		av_free(m_context);
		m_context = NULL;
	}
}

bool AudioTranscoder::isEncoderOpen()
{
	return (m_context != NULL);
}

bool AudioTranscoder::isSamleFormat(int fmt, int channels, int sampleRate)
{
	return (m_channels == channels) &&
        (m_sampleRate == sampleRate) &&
        (fmt == m_context->sample_fmt);
}

bool AudioTranscoder::getAudioConfig(std::string& config)
{
    config = m_extradata;
	return true;
}


void AudioTranscoder::convertAndStore(AVFramePtr& frame)
{
    if (frame->channel_layout == 0)
    {
        frame->channel_layout = av_get_default_channel_layout(frame->channels);
    }

	if (isSamleFormat(frame->format, frame->channels, frame->sample_rate))
	{
		storeFrame(frame);
	}
	else
	{
		/// resample
        AVFramePtr outFrame(av_frame_alloc(), ffmpeg::AVFrameDeleter());

		outFrame->format = m_context->sample_fmt;
		outFrame->channels = m_context->channels;
		outFrame->channel_layout = m_context->channel_layout;
		outFrame->sample_rate = m_context->sample_rate;
		outFrame->pts = frame->pts;
        outFrame->nb_samples = frame->nb_samples;
        av_frame_get_buffer(outFrame.get(), 1);

		int rc = resample(frame.get(), outFrame.get());
		if (rc > 0)
		{
			storeFrame(outFrame);
		}
		else
		{
			CLog::error("resample failed. \n");
		}
	}
}

int AudioTranscoder::resample(AVFrame* inFrame, AVFrame* outFrame)
{
	//int samples = outFrame->nb_samples;
	//int linesize = outFrame->linesize[0];

    int rc = 0;
    if (m_swrContext == NULL)
    {
        m_swrContext = swr_alloc();

        rc = swr_config_frame(m_swrContext, outFrame, inFrame);

        rc = swr_init(m_swrContext);
        if (rc != 0)
        {
            CLog::error("AudioTranscoder. swr_init failed. rc:%d\n", rc);
        }

        //rc = swr_is_initialized(m_swrContext);
    }
    else
    {
        int ret = swr_config_frame(m_swrContext, outFrame, inFrame);
    }

    int ret = swr_convert_frame(m_swrContext, outFrame, inFrame);

	//assert(samples == outFrame->nb_samples);
	//assert(linesize == outFrame->linesize[0]);

    return outFrame->nb_samples;
}

void AudioTranscoder::storeFrame(AVFramePtr& frame)
{
	if (!m_fifo)
	{
		m_fifo = av_audio_fifo_alloc((AVSampleFormat)frame->format, frame->channels, frame->nb_samples);
	}

    av_audio_fifo_write(m_fifo, (void**)frame->data, frame->nb_samples);
}

bool AudioTranscoder::loadFrame(AVFrame* frame, int nb_samples)
{
	if (!m_fifo)
	{
		return false;
	}

	if (av_audio_fifo_size(m_fifo) < nb_samples)
	{
		return false;
	}

	av_audio_fifo_read(m_fifo, (void**)frame->data, nb_samples);
	return true;
}

void AudioTranscoder::closeFifo()
{
	if (m_fifo)
	{
		av_audio_fifo_free(m_fifo);
		m_fifo = NULL;
	}
}

int AudioTranscoder::getFifoSize()
{
	if (!m_fifo)
	{
		return 0;
	}
	return av_audio_fifo_size(m_fifo);
}

int AudioTranscoder::doEncode(AVPacket* pkt)
{
	AVFramePtr encFrame(av_frame_alloc(), ffmpeg::AVFrameDeleter());
	AVFrame* frame = encFrame.get();
	frame->nb_samples = m_context->frame_size;
	frame->channels = m_context->channels;
	frame->channel_layout = m_context->channel_layout;
	frame->sample_rate = m_context->sample_rate;
	frame->format = m_context->sample_fmt;

	av_frame_get_buffer(frame, 0);

	if (!loadFrame(encFrame.get(), m_context->frame_size))
	{
		return ENOMEM;
	}

    m_sampleCount += m_context->frame_size;
    frame->pts = m_sampleCount;

	int got_packet = 0;
	int rc = avcodec_encode_audio2(m_context, pkt, frame, &got_packet);
	if (rc == 0)
	{
		rc = got_packet ? 0 : EAGAIN;
	}

	return rc;
}

int AudioTranscoder::openDecoder()
{
	AVCodec* pCodec = avcodec_find_decoder((AVCodecID)m_fmt.m_audioCodec);
	if (!pCodec)
	{
		return EBADF;
	}

	m_decContext = avcodec_alloc_context3(pCodec);

	m_decContext->refcounted_frames = 1;

	m_decContext->codec_type = AVMEDIA_TYPE_AUDIO;
	m_decContext->channels = m_fmt.m_channels;
	m_decContext->sample_rate = m_fmt.m_sampleRate;

	if (m_fmt.m_audioConfig.size())
	{
		m_decContext->extradata = (uint8_t*)av_memdup(m_fmt.m_audioConfig.c_str(), m_fmt.m_audioConfig.size());
		m_decContext->extradata_size = m_fmt.m_audioConfig.size();
	}

	if (avcodec_open2(m_decContext, pCodec, NULL) < 0)
	{
		avcodec_close(m_decContext);
		av_free(m_decContext);
		m_decContext = NULL;
		return EBADF;
	}

	return 0;
}

void AudioTranscoder::closeDecoder()
{
	if (m_decContext)
	{
		avcodec_close(m_decContext);
		av_free(m_decContext);
		m_decContext = NULL;
	}
}



} /* namespace av */
